package com.frontalcode 
{
    public class StylesheetParser extends Tokenizer
	{
		/*
			Variable: nextOrderKey
			
				We return an order key for each stylesheet we parse. This is to
				support the ordering of selectors when applying more than one at
				a time to an element. (Styles declared later have precedence
				over styles declared earlier.)
		*/
		static private var nextOrderKey : Number = 0;
		
		/*
			Variable: nextSelectorOrderKey
			
				We also keep track of the order of each selector within each
				stylesheet. We keep these separate in case we ever want to
				re-parse a stylesheet and want to preserve its order relative to
				other stylesheets.
		*/
		private var nextSelectorOrderKey : Number;
		
		static private var _instance : StylesheetParser = null;
		
		static internal var STYLESHEET_TOKENS : Array = [
				{ token: "/*",			selfDelimiting: true },
				{ token: "*/",			selfDelimiting: true },
				{ token: "//",			selfDelimiting: true },	// Not CSS2.
				{ token: "*",			selfDelimiting: true },
				{ token: ".",			selfDelimiting: true },
				{ token: ",",			selfDelimiting: true },
				{ token: ":",			selfDelimiting: true },
				{ token: "|",			selfDelimiting: true },
				{ token: ";",			selfDelimiting: true },
				{ token: ">",			selfDelimiting: true, isCombinator: true },
				{ token: "+",			selfDelimiting: true, isCombinator: true },
				{ token: "~",			selfDelimiting: true, isCombinator: true },
				{ token: "[",			selfDelimiting: true },
				{ token: "]",			selfDelimiting: true },
				{ token: "(",			selfDelimiting: true },
				{ token: ")",			selfDelimiting: true },
				{ token: "@",			selfDelimiting: true },	// Our attribute operator. Not CSS2.
				{ token: "=",			selfDelimiting: true, isAttrSelectorOp: true },
				{ token: "#",			selfDelimiting: true },
				{ token: "~=",			selfDelimiting: true, isAttrSelectorOp: true },
				{ token: "'",			selfDelimiting: true },
				{ token: "\"",			selfDelimiting: true },
				{ token: "|=",			selfDelimiting: true, isAttrSelectorOp: true },
				{ token: "^=",			selfDelimiting: true, isAttrSelectorOp: true },
				{ token: "$=",			selfDelimiting: true, isAttrSelectorOp: true },
				{ token: "*=",			selfDelimiting: true, isAttrSelectorOp: true },
				{ token: "link",		selfDelimiting: false, isPseudoClass: true, isIdentifier: true },
				{ token: "visited",		selfDelimiting: false, isPseudoClass: true, isIdentifier: true },
				{ token: "active",		selfDelimiting: false, isPseudoClass: true, isIdentifier: true },
				{ token: "hover",		selfDelimiting: false, isPseudoClass: true, isIdentifier: true },
				{ token: "selected",	selfDelimiting: false, isPseudoClass: true, isIdentifier: true },	// Not CSS2.
				{ token: "disabled",	selfDelimiting: false, isPseudoClass: true, isIdentifier: true },	// Not CSS2.
				{ token: "focus",		selfDelimiting: false, isPseudoClass: true, isIdentifier: true },
				{ token: "lang",		selfDelimiting: false, isPseudoClass: true, isIdentifier: true },
				{ token: "first-child",	selfDelimiting: false, isPseudoClass: true, isIdentifier: true },
				{ token: "not",			selfDelimiting: false, isPseudoClass: true, isIdentifier: true },
				{ token: "{",			selfDelimiting: true },
				{ token: "\\",			selfDelimiting: true },
				{ token: "}",			selfDelimiting: true }
			];
			
		// For tokenizer.
		//
		
		private var tokenStream : Object;
	
		// For parser.
		//
		
		private var currentCode : String;
		private var currentStatementCodeIndex : Number;
		private var targetDocElemStyles : DocElemStyles;
		
		function StylesheetParser ( )
		{
			super ( STYLESHEET_TOKENS );
			
			_instance = this;
		}
		
		static internal function getInstance ( ) : StylesheetParser
		{
			if ( _instance == null )
			{
				new StylesheetParser ( );
			}
			
			return _instance;
		}
		                                
		// Convenience method.
		//
		static public function gI ( ) : StylesheetParser { return getInstance ( ); }
		
		/*
			Function: getNextStylesheetOrderKey
			
				Get the next order key for a stylesheet. This is a public 
				internal function because every DocumentElement acts like its own
				stylesheet in order to support it style attribute.
		*/
		static public function getNextStylesheetOrderKey ( ) : Number { return nextOrderKey++; }
		
		internal function parse ( code : String, docElemStyles : DocElemStyles ) : Number
		{
			if ( code != null && code.length > 0 )
			{
				currentCode = code;
				currentStatementCodeIndex = undefined;
		
				try
				{
					// Parse the code into a token stream.
					//
					
					tokenStream = tokenizeCode ( code, Util.isTrue.test ( docElemStyles.getAttribute ( "debug" ) ) );
					
					// Add the special end-of-input token.
					//
					
					tokenStream.tokens.push ( { token: "EndOfInput" } );
					
					targetDocElemStyles = docElemStyles;
					parseStylesheet ( );
				}
				catch ( error : Error )
				{
					Debugger.logMessage ( Debugger.ERROR, "StylesheetParser", error.toString ( ) );
				}
				finally
				{
					debug = false;
				}
			}
		
			return nextOrderKey++;
		}
		
		internal function parseStylesheet ( ) : void
		{
			currentStatementCodeIndex = 0;
			nextSelectorOrderKey = 0;
			while ( peek ( ).token != "EndOfInput" ) parseRuleSet ( );
		}
		
		internal function parseRuleSet ( ) : void
		{
			currentStatementCodeIndex = peek ( ).codeIndex;
			
			var selectors : Array = [ ];
			
			while ( true )
			{
				// We track the order of each selector for later cascade 
				// ordering.
				//
				// http://www.w3.org/TR/CSS2/cascade.html#cascading-order
				//
				var selector : Object = parseSelector ( );
				selector.stylesheetOrder = nextOrderKey;
				selector.order = nextSelectorOrderKey++;
				selectors.push ( selector );
				if ( peek ( ).token != "," ) break;
				expect ( [ "," ] );
			}
			
			expect ( [ "{" ] );
			var declarations : Array = [ ];
			while ( peek ( ).token != "}" ) declarations.push ( parseDeclaration ( ) );
			expect ( [ "}" ] );
			
			if ( selectors.length > 0 ) targetDocElemStyles.addStyleRuleSet ( selectors, declarations );
		}
		
		internal function parseSelector ( ) : Object
		{
			var result : Object = { objectType: "Selector", selectorPieces: [ ], ids : 0, otherAttrsAndPseudoClasses: 0, elementNames: 0 };
			
			// As we go, calculate our specificity. This is used to determine 
			// the ordering of selectors.
			//
			// http://www.w3.org/TR/CSS2/cascade.html#specificity
			//
			while ( true )
			{
				var simpleSelector : Object = parseSimpleSelector ( );
				result.selectorPieces.push ( simpleSelector );
				result.ids							+= simpleSelector.ids;
				result.otherAttrsAndPseudoClasses	+= simpleSelector.otherAttrsAndPseudoClasses;
				result.elementNames					+= simpleSelector.elementNames;

				if ( peek ( ).token == "," || peek ( ).token == "{" ) break;
				if ( peek ( ).isCombinator ) result.selectorPieces.push ( { objectType: "Combinator", value: next ( ).token } );
			}
			
			return result;
		}
		
		internal function parseSimpleSelector ( isNegation : Boolean = false ) : Object
		{
			// The counts are used to calculate our specificity later.
			//
			var result : Object = { objectType: "SimpleSelector", onlyNoNamespace: false, namespace: null, constraints: [ ], ids : 0, otherAttrsAndPseudoClasses: 0, elementNames: 0 };
			
			var precedesWhitespace : Boolean = false;
			
			if ( peek ( ).token == "*" || peek ( ).token == "Identifier" || peek ( ).token == "|" )
			{
				var tokenDefn : Object = next ( );
				if ( tokenDefn.token == "|" )
				{
					// Only match elements without a namespace.
					result.onlyNoNamespace = true;
				}
				else if ( peek ( ).token == "|" )
				{
					// It's a namespace.
					next ( );
					if ( tokenDefn.token == "Identifier" ) result.namespace = tokenDefn.value; 
					tokenDefn = next ( );
				}
				if ( tokenDefn.token == "*" )
				{
					precedesWhitespace = tokenDefn.precedesWhitespace;
					result.isUniversalSelector = true;
				}
				else if ( tokenDefn.token == "Identifier" )
				{
					precedesWhitespace = tokenDefn.precedesWhitespace;
					result.elementName = tokenDefn.value;
					result.elementNames++;
				}
			}
			else
			{
				result.isUniversalSelector = true;
			}
			
			while ( ! precedesWhitespace && ! peek ( ).isCombinator && peek ( ).token != "," && ( ! isNegation || peek ( ).token != ")" ) )
			{
				tokenDefn = next ( );
				switch ( tokenDefn.token )
				{
					case "#":
						result.constraints.push ( { objectType: "IdSelector", name: parseIdentifier ( ).name } );
						result.ids++;
						break;
						
					case ".":
						result.constraints.push ( { objectType: "ClassSelector", name: parseIdentifier ( ).name } );
						result.otherAttrsAndPseudoClasses++;
						break;
						
					case "[":
						result.constraints.push ( parseAttributeSelector ( ) );
						result.otherAttrsAndPseudoClasses++;
						break;
						
					case ":":
						if ( peek ( ).token == "not" )
						{
							var negation : Object = parseNegationPseudoClass ( );
							result.ids += negation.simpleSelector.ids;
							result.otherAttrsAndPseudoClasses += negation.simpleSelector.otherAttrsAndPseudoClasses;
							result.elementNames += negation.simpleSelector.elementNames;
							result.constraints.push ( negation );
						}
						else
						{
							result.constraints.push ( parsePseudoClass ( ) );
							result.otherAttrsAndPseudoClasses++;
						}
						break;
					
					default:
						throw new FrontalError ( "\"" + tokenDefn.token + "\" at " + getStylesheetSnippet ( tokenDefn.codeIndex ), FrontalError.UNKNOWN_OPERATOR );
				}
				
				precedesWhitespace = peek ( ).succedesWhitespace;
			}
			
			return result;
		}
		
		/*
			Function: parseNegationPseudoClass
			
				See http://www.w3.org/TR/css3-selectors/#negation. The negation
				pseudo-class "is a functional notation taking a simple selector
				(excluding the negation pseudo-class itself and pseudo-elements)
				as an argument."
						
				Selectors inside the negation pseudo-class are counted like any
				other, but the negation itself does not count as a pseudo-class.
		*/
		internal function parseNegationPseudoClass ( ) : Object
		{
			var result : Object = { objectType: "NegationPseudoClass" };
			
			expect ( [ "not", "(" ] );
			result.simpleSelector = parseSimpleSelector ( true );
			result.simpleSelector.negate = true;
			expect ( [ ")" ] );
			
			return result;
		}
		
		internal function parseAttributeSelector ( ) : Object
		{
			// We allow tokens to act as identifiers here. For example, "lang" 
			// is a valid attribute name.
			//
			var tokenDefn : Object = peek ( );
			var result : Object = { objectType: "AttributeSelector", attribute: tokenDefn.isIdentifier ? next ( ).token : parseIdentifier ( ).name };
			
			if ( peek ( ).isAttrSelectorOp )
			{
				result.op = next ( ).token;
				tokenDefn = next ( );
				if ( tokenDefn.token != "Identifier" && tokenDefn.token != "String" ) throw new FrontalError ( "Found \"" + tokenDefn.token + "\". Expected string or identifier at " + getStylesheetSnippet ( tokenDefn.codeIndex ), FrontalError.UNEXPECTED_TOKEN );
				result.value = tokenDefn.value;
			}
			
			expect ( [ "]" ] );
			
			return result;
		}
		
		internal function parsePseudoClass ( ) : Object
		{
			var tokenDefn : Object = next ( );
			
			// We allow for arbitrary pseudoclasses.
			
			if ( ! tokenDefn.isPseudoClass && tokenDefn.token != "Identifier" ) throw new FrontalError ( "Found \"" + tokenDefn.token + "\". Expected pseudo-class at " + getStylesheetSnippet ( tokenDefn.codeIndex ), FrontalError.UNEXPECTED_TOKEN );
			
			var result : Object = { objectType: "PseudoClassSelector", name: tokenDefn.isPseudoClass ? tokenDefn.token : tokenDefn.value };
			
			if ( tokenDefn.token == "lang" || ( tokenDefn.token == "Identifier" && peek ( ).token == "(" ) ) 
			{
				expect ( [ "(" ] );
				result.value = next ( ).value;
				expect ( [ ")" ] );
			}
				 
			return result;
		}
		
		internal function parseIdentifier ( ) : Object
		{
			if ( peek ( ).token != "Identifier" ) throw new FrontalError ( "Found \"" + peek ( ).token + "\". Expected identifier at " + getStylesheetSnippet ( peek ( ).codeIndex ), FrontalError.UNEXPECTED_TOKEN );
			return { objectType: "Identifier", name: next ( ).value };
		}
		
		internal function parseDeclaration ( ) : Object
		{
			var result : Object = { };
			
			if ( peek ( ).token == "@" || peek ( ).token == "+" )
			{
				if ( next ( ).token == "@" )
				{
					result.attribute = parseIdentifier ( ).name;
					if ( peek ( ).token == "|" )
					{
						// It has a namespace.
						//
						next ( );
						result.attributeNamespace = result.attribute;
						result.attribute = parseIdentifier ( ).name;
					}
				}
				else
				{
					result.action = parseIdentifier ( ).name;
				}
				
				if ( peek ( ).token == "{" )
				{
					expect ( [ "{" ] );
					result.value = next ( ).value;
					expect ( [ "}" ] );
				}
				else
				{
					expect ( [ ":" ] );
					result.value = next ( ).value;
				}
			}
			else
			{
				// We allow tokens to act as identifiers here. For example, 
				// "disabled" is a valid style name.
				//
				var tokenDefn : Object = peek ( );
				result.style = tokenDefn.isIdentifier ? next ( ).token : parseIdentifier ( ).name;
				expect ( [ ":" ] );
				result.value = next ( ).value;
				if ( peek ( ).token == ";" ) expect ( [ ";" ] );
			}
			
			return result;
		}
		
		/*
			Function: getNextToken
			
				Return the next token found in the raw character stream.
			
			Parameters:
			
				codeStream - See internalEvaluate ( ) for structure of codeStream parameter.
			
			Returns:
				
				The token descriptor.
		*/
		internal function getNextToken ( codeStream : Object ) : Object
		{
			// Skip whitespace.
			//
			
			var nextChar : String;
			var succedesWhitespace : Boolean = false;
			
			while ( ( nextChar = codeStream.code.charAt ( codeStream.index ) ) == ' ' || nextChar == '\t' || nextChar == '\r' || nextChar == '\n' )
			{
				codeStream.index++;
				succedesWhitespace = true;
			}
			
			if ( codeStream.index >= codeStream.code.length )
			{
				return undefined;
			}
				
			var tokenDefn : Object;
			
			// Find a matching, self-delimiting token.
			//
			
			if ( selfDelimitingTokenCheck.indexOf ( codeStream.code.charAt ( codeStream.index ) ) >= 0 )
			{
				var scanString : String = codeStream.code.substr ( codeStream.index, allTokens [ 0 ].token.length );
				
				while ( scanString.length > 0 )
				{
					tokenDefn = copyObject ( allSelfDelimitingTokensHash [ scanString ] );
					
					if ( tokenDefn != null )
					{
						break;
					}
					
					scanString = scanString.substr ( 0, scanString.length - 1 );
				}
			}
			
			if ( tokenDefn == null )
			{
				var scan : Number = codeStream.index + ( tokenDefn == null ? 0 : 1 );
				nextChar = codeStream.code.charAt ( scan ).toLowerCase ( );
				
				// It is an identifier.
				//
				
				if ( nextChar != "_" && ( nextChar < "a" || nextChar > "z" ) )
				{
					throw new FrontalError ( "Identifier cannot start with \"" + nextChar + "\" at " + getStylesheetSnippet ( codeStream.index ) );
				}
				
				while ( ( ( nextChar = codeStream.code.charAt ( ++scan ).toLowerCase ( ) ) >= "0" && nextChar <= "9" ) || nextChar == "-" || nextChar == "_" || ( nextChar >= "a" && nextChar <= "z" ) ) { }
	
				var identifier : String = codeStream.code.substring ( codeStream.index, scan );
				
				// Check if it's a token.
				//
				
				if ( allTokensHash.hasOwnProperty ( identifier ) )
				{
					tokenDefn = copyObject ( allTokensHash [ identifier ] ); 
				}
				else
				{
					tokenDefn = {
							value: identifier,
							token: "Identifier"
						};
				}
			}
			
			tokenDefn.codeIndex = codeStream.index;
			
			if ( tokenDefn.value != undefined )             
			{
				codeStream.index += tokenDefn.value.length;
			}
			else
			{
				codeStream.index += tokenDefn.token.length;
			}
			
			if ( ( nextChar = codeStream.code.charAt ( codeStream.index ) ) == ' ' || nextChar == '\t' || nextChar == '\r' || nextChar == '\n' || codeStream.index >= codeStream.code.length )
			{
				tokenDefn.precedesWhitespace = true;
			}
			
			tokenDefn.succedesWhitespace = succedesWhitespace;
			
			return tokenDefn;
		}
		
		/*
			Function: tokenizeCode
			
				Convert a string of raw characters into a stream of tokens.
				
				We also cleanup the token stream by converting style class and 
				individual attribute definitions into String tokens.
				
			Returns:
			
				The tokenized code stream.
		*/
		override internal function tokenizeCode ( code : String, debug : * ) : Object
		{
			if ( debug != null ) this.debug = debug;
			var codeStream : Object = { code: code, index: 0 };
			var tokenStream : Array = new Array ( );
			var braceCount : Number = 0;
			
			while ( codeStream.index < codeStream.code.length )
			{
				var tokenDefn : Object = getNextToken ( codeStream );
				
				if ( tokenDefn == null )
				{
					// Created by trailing spaces.
					//
					
					break;
				}
				
				// Do some special processing on comments and attribute definitions.
				//
				
				var discardToken : Boolean = false;
				var addString : Boolean = false;
				var value : String;
				
				switch ( tokenDefn.token )
				{
					case "//":
						// Jump to the next line.
						//
						var nextCR : Number = codeStream.code.indexOf ( "\r", codeStream.index );
						if ( nextCR < 0 ) nextCR = codeStream.code.length;
						var nextNL : Number = codeStream.code.indexOf ( "\n", codeStream.index );
						if ( nextNL < 0 ) nextNL = codeStream.code.length;
						codeStream.index = Math.min ( nextCR, nextNL );
						while ( codeStream.index < codeStream.code.length && "\r\n".indexOf ( codeStream.code.charAt ( codeStream.index ) ) == 0 ) codeStream.index++;
						discardToken = true;
						break;
						
					case "/*":
						// Jump to the close comment tag.
						//
				
						var closeComment : Number = codeStream.code.indexOf ( "*/", codeStream.index );
						
						if ( closeComment >= 0 )
						{
							codeStream.index = closeComment + 2;
						}
						
						discardToken = true;
						break;
				
					case "}":
						braceCount--;
						break;
						
					case "{":
						braceCount++;
						
						// If we are more than 2 braces deep and we are an
						// attribute then get everything up to the next brace.
						//
						if ( braceCount == 2 )
						{
							if ( 
									tokenStream.length < 2 || 
									( 
										tokenStream [ tokenStream.length - 2 ].token != "@" && 
										tokenStream [ tokenStream.length - 2 ].token != "+" &&
										( tokenStream.length < 4 || tokenStream [ tokenStream.length - 2 ].token != "|" || tokenStream [ tokenStream.length - 4 ].token != "@" )
									)
								)
							{
								throw new FrontalError ( "Unexpected token \"{\" at " + getStylesheetSnippet ( codeStream.index ) );
							}

							var nextOpen : Number = codeStream.code.indexOf ( "{", codeStream.index );
							var nextClose : Number = codeStream.code.indexOf ( "}", codeStream.index );
							
							while ( nextOpen >= 0 && nextOpen < nextClose )
							{
								nextOpen = codeStream.code.indexOf ( "{", nextOpen + 1 );
								nextClose = codeStream.code.indexOf ( "}", nextClose + 1 );
							}
							
							value = codeStream.code.substring ( codeStream.index, nextClose );
							addString = true;
						}
						break;
						
					case ":":
						if ( braceCount >= 1 )
						{
							if ( 
									tokenStream.length < 2 || 
									( 
										tokenStream [ tokenStream.length - 2 ].token != "@" && 
										tokenStream [ tokenStream.length - 2 ].token != "+" &&
										( tokenStream.length < 4 || tokenStream [ tokenStream.length - 2 ].token != "|" || tokenStream [ tokenStream.length - 4 ].token != "@" )
									)
								)
							{
								// If we are a style then get everything up to the semi-
								// colon or brace.
								//
								var nextSemiColon : Number = codeStream.code.indexOf ( ";", codeStream.index );
								nextClose = codeStream.code.indexOf ( "}", codeStream.index );
								if ( nextSemiColon >= 0 ) nextClose = nextClose < 0 ? nextSemiColon : Math.min ( nextSemiColon, nextClose );
								
								if ( nextClose < 0 )
								{
									throw new FrontalError ( "Expected \";\" or \"}\" at " + getStylesheetSnippet ( codeStream.index ), FrontalError.UNEXPECTED_TOKEN );
								}
								
								value = codeStream.code.substring ( codeStream.index, nextClose );
							}
							else
							{
								// If we are an attribute then get everything on this 
								// line.
								//
								nextCR = codeStream.code.indexOf ( "\r", codeStream.index );
								if ( nextCR < 0 ) nextCR = codeStream.code.length - 1;
								nextNL = codeStream.code.indexOf ( "\n", codeStream.index );
								if ( nextNL < 0 ) nextNL = codeStream.code.length - 1;
								value = codeStream.code.substring ( codeStream.index, Math.min ( nextCR, nextNL ) );
							}
							
							addString = true;
						}
						
						break;
						
					case "(":
						if ( tokenStream.length >= 2 && tokenStream [ tokenStream.length - 1 ].token != "not" && tokenStream [ tokenStream.length - 2 ].token == ":" )
						{
							var nextParen : Number = codeStream.code.indexOf ( ")", codeStream.index );
							
							if ( nextParen < 0 )
							{
								throw new FrontalError ( "Expected \")\" at " + getStylesheetSnippet ( codeStream.index ), FrontalError.UNEXPECTED_TOKEN );
							}
							
							value = codeStream.code.substring ( codeStream.index, nextParen );
							addString = true;
						}
						break;
						
					case "\"":
					case "'":
						// We want to find the next unescaped quote and use the
						// skipped characters as a String.
						//
						var nextQuote : Number = codeStream.code.indexOf ( tokenDefn.token, codeStream.index );
						
						while ( nextQuote > 0 && codeStream.code.charAt ( nextQuote - 1 ) == "\\" && codeStream.code.charAt ( nextQuote - 2 ) != "\\" )
						{
							nextQuote = codeStream.code.indexOf ( tokenDefn.token, nextQuote + 1 );
						}
						
						if ( nextQuote < 0 )
						{
							throw new FrontalError ( "Unmatched quote: " + codeStream.code.substr ( codeStream.index - 1 ) + " at " + getStylesheetSnippet ( codeStream.index ) );
						}
						
						var delimited : String = codeStream.code.substring ( codeStream.index, nextQuote );
						var stringLiteral : String = "";
						
						// Unescape any escapes.
						//
						var scan : Number = 0;
						
						while ( scan < delimited.length )
						{
							var nextChar : String = delimited.charAt ( scan );
							
							if ( nextChar == "\\" )
							{
								if ( scan + 1 >= delimited.length )
								{
									throw new FrontalError ( "Incomplete escape in string: " + delimited + " at " + getStylesheetSnippet ( codeStream.index ) );
								}
								
								switch  ( delimited.charAt ( scan + 1 ) )
								{
									case "\\":
										stringLiteral += "\\";
										break;
										
									case "b":
										stringLiteral += "\b";
										break;
										
									case "t":
										stringLiteral += "\t";
										break;
										
									case "n":
										stringLiteral += "\n";
										break;
										
									case "v":
										stringLiteral += "\v";
										break;
										
									case "f":
										stringLiteral += "\f";
										break;
										
									case "r":
										stringLiteral += "\r";
										break;
										
									case "'":
										stringLiteral += "'";
										break;
										
									case "\"":
										stringLiteral += "\"";
										break;
										
									default:
										throw new FrontalError ( "Unknown escape code in string: " + delimited + " at " + getStylesheetSnippet ( codeStream.index ) );
										break;
								}
								
								scan += 2;
							}
							else
							{
								stringLiteral += nextChar;
								scan++;
							}
						}
						
						// Remove the quote token.
						//
						discardToken = true;
						
						// Add the StringLiteral token.
						//
						tokenStream.push ( { value: stringLiteral, token: "String" } );
			
						if ( debug ) trace ( "add token " + Debugger.objectToString ( tokenStream [ tokenStream.length - 1 ], 0, 2, false ) );
						
						// Remove the closing quote.
						//
						codeStream.index = nextQuote + 1;
						
						break;
				}
				
				if ( ! discardToken )
				{
					tokenStream.push ( tokenDefn );
							
					if ( debug ) trace ( "add token " + Debugger.objectToString ( tokenStream [ tokenStream.length - 1 ], 0, 2, false ) );
				}
				
				if ( addString )
				{
					// Add the String token.
					//
					tokenStream.push ( { value: Util.trim ( value ), token: "String" } );
					codeStream.index += value.length;
					
					if ( debug ) trace ( "add token " + Debugger.objectToString ( tokenStream [ tokenStream.length - 1 ], 0, 2, false ) );
				}
			}
			
			return { tokens: tokenStream, index: 0 };
		}
		
		/*
			Function: peek
			
				Parser helper function - return the current token being processed.
				
			Returns:
			
				A token definition.
		*/
		internal function peek ( ) : Object
		{
			return tokenStream.tokens [ tokenStream.index ];
		}
		
		/*
			Function: next
			
				Parser helper function - consume the current token.
				
			Returns:
			
				The consumed token.
		*/
		internal function next ( ) : Object
		{
			return tokenStream.tokens [ tokenStream.index++ ];
		}
		
		/*
			Function: expect
			
				Consume the expected tokens. 
				
			Parameters:
			
				tokens	- An array of expected tokens.
				
			Exceptions:
			
				Throws an error if the tokens in the token stream were not expected.
		*/
		internal function expect ( tokens : Array ) : void
		{
			for ( var i : int = 0; i < tokens.length; i++ )
			{
				var nextToken : Object = next ( );
				
				if ( nextToken.token != tokens [ i ] )
				{
					throw new FrontalError ( "Unexpected token \"" + nextToken.token + "\". Expected \"" + tokens [ i ] + "\" at " + getStylesheetSnippet ( nextToken.codeIndex ) );
				}
			}
		}
		
		internal function getStylesheetSnippet ( codeIndex : Number ) : String
		{
			return super.getCodeSnippet ( currentCode, currentStatementCodeIndex, codeIndex );
		}
	}
}
